package com.atguigu.gmall.index.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.index.annotation.GmallCache;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class GmallCacheAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RBloomFilter bloomFilter;

    /*@Pointcut("execution(* com.atguigu.gmall.index.service.*.*(..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void before(){
        System.out.println("============2前置通知=============");
    }

    @AfterReturning(value = "pointcut()", returning = "result")
    public void afterReturning(Object result){
        System.out.println("============4返回后通知=============" + result);
    }

    @AfterThrowing(value = "pointcut()", throwing = "ex")
    public void afterThrowing(Exception ex){
        System.out.println("============异常后通知=============" + ex.getMessage());
    }

    @After("pointcut()")
    public void after(){
        System.out.println("============5最终通知=============");
    }*/

    @Around("@annotation(com.atguigu.gmall.index.annotation.GmallCache)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        
        // 获取方法对象的签名
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        // 获取目标方法对象
        Method method = signature.getMethod();
        // 获取方法对象上的注解对象
        GmallCache gmallCache = method.getAnnotation(GmallCache.class);

        // 缓存前缀
        String prefix = gmallCache.prefix();
        // 目标方法的参数列表
        Object[] args = joinPoint.getArgs();
        String argString = StringUtils.join(args, ",");
        // 组装缓存的key
        String key = prefix + argString;
        // 1.先查询缓存，如果缓存命中则直接返回
        String json = this.redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(json)){
            return JSON.parseObject(json, method.getReturnType());
        }

        // 为了解决缓存穿透，使用布隆过滤器
        if (!bloomFilter.contains(key)) {
            return null;
        }

        // 2.添加分布式锁，防止缓存击穿
        RLock fairLock = this.redissonClient.getFairLock(gmallCache.lock() + argString);
        fairLock.lock();

        try {
            // 3.再次查询缓存如果命中则直接返回（获取锁的过程中可能有其他请求把数据放入了缓存）
            String json2 = this.redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(json2)){
                return JSON.parseObject(json2, method.getReturnType());
            }

            // 4.执行目标方法查询数据库
            Object result = joinPoint.proceed(args);

            // 5.把查询结果放入缓存 解决缓存雪崩
            int timeout = gmallCache.timeout() + new Random().nextInt(gmallCache.random());
            this.redisTemplate.opsForValue().set(key, JSON.toJSONString(result), timeout, TimeUnit.MINUTES);

            return result;
        } finally {
            // 6.释放分布式锁
            fairLock.unlock();
        }
    }
}
